home *** CD-ROM | disk | FTP | other *** search
/ Magnum One / Magnum One (Mid-American Digital) (Disc Manufacturing).iso / d12 / v9n09.arc / PAN.ASM < prev    next >
Assembly Source File  |  1990-04-09  |  61KB  |  2,297 lines

  1.     title    'PAN:  Program ANimator by Pete Maclean'
  2.  
  3.     include    pan.hdr
  4.  
  5. ; Symbol definitions
  6.  
  7. CR        =    13    ; ASCII carriage return
  8. LF        =    10    ; ASCII linefeed
  9. TAB        =    9    ; ASCII Tab
  10.  
  11. ; BIOS Keyboard Buffer definitions
  12.  
  13. KBB_SEGADD    =    40h    ; segment address of buffer
  14. KBB_HEAD    =    1Ah    ; offset to head pointer
  15. KBB_TAIL    =    1Ch    ; offset to tail pointer
  16. KBB_START    =    80h    ; offset to start pointer
  17. KBB_END        =    82h    ; offset to end pointer
  18.  
  19. ; PAN States
  20.  
  21. PS_INITIAL    =    0    ; initial state - no target program loaded
  22. PS_LOADED    =    1    ; target program loaded
  23. PS_RUNNING    =    2    ; target program running
  24. PS_OBIT        =    3    ; waiting for target program to die
  25. PS_QUIT        =    4    ; QUIT pending when target program dies
  26.  
  27. code    segment    para public 'code'
  28.     assume    cs:code, ds:code
  29.     org    100h
  30. start:    jmp    main        ; entry point
  31.  
  32. ; Messages
  33.  
  34. initmsg        db    'PAN 1.0 (c) 1990 Ziff Communications Co.',CR,LF
  35.         db    'PC Magazine ',254,' Pete Maclean',CR,LF,'$'
  36.  
  37. crlfz        db    CR,LF,0
  38.  
  39. ; Definition for command-table entry
  40.  
  41. COMMAND        STRUC
  42. PC_KEY        dw    ?    ; (offset) address of command key
  43. PC_PROC        dw    ?    ; (offset) address of command processor
  44. PC_TYPE        db    ?    ; coded command type
  45. COMMAND        ENDS
  46.  
  47. command_entry_size    db    SIZE COMMAND
  48.  
  49. ; Command types
  50.  
  51. PCT_REG        =    0    ; regular command
  52. PCT_IF        =    2    ; If command
  53. PCT_ELSE    =    4    ; Else command
  54. PCT_FI        =    6    ; EndIf command
  55.  
  56. ; Command table
  57.  
  58. command_table    LABEL    COMMAND
  59.         COMMAND    <k_Break,    c_Break,    PCT_REG>
  60.         COMMAND    <k_Cursor,    c_Cursor,    PCT_REG>
  61.         COMMAND    <k_Else,    c_Else,        PCT_ELSE>
  62.         COMMAND    <k_EndIf,    c_EndIf,    PCT_FI>
  63.         COMMAND    <k_Flush,    c_Flush,    PCT_REG>
  64.         COMMAND    <k_GetKey,    c_GetKey,    PCT_REG>
  65.         COMMAND    <k_Go,        c_Go,        PCT_REG>
  66.         COMMAND    <k_IfKey,    c_IfKey,    PCT_IF>
  67.         COMMAND    <k_IfLoad,    c_IfLoad,    PCT_IF>
  68.         COMMAND    <k_IfScreen,    c_IfScreen,    PCT_IF>
  69. jump_command    COMMAND    <k_Jump,    c_Jump,        PCT_REG>
  70.         COMMAND    <k_Key,        c_Key,        PCT_REG>
  71. label_command    COMMAND    <k_Label,    c_Label,    PCT_REG>
  72.         COMMAND    <k_Load,    c_Load,        PCT_REG>
  73.         COMMAND    <k_Lock,    c_Lock,        PCT_REG>
  74.         COMMAND    <k_Mode,    c_Mode,        PCT_REG>
  75.         COMMAND    <k_Pause,    c_Pause,    PCT_REG>
  76.         COMMAND    <k_Output,    c_Output,    PCT_REG>
  77.         COMMAND    <k_Quit,    c_Quit,        PCT_REG>
  78.         COMMAND    <k_Screen,    c_Screen,    PCT_REG>
  79. setif_command    COMMAND    <k_SetIf,    c_SetIf,    PCT_REG>
  80.         COMMAND    <k_TypeRate,    c_TypeRate,    PCT_REG>
  81.         COMMAND    <k_Unlock,    c_Unlock,    PCT_REG>
  82.         COMMAND    <k_Video,    c_Video,    PCT_REG>
  83.         COMMAND    <k_WaitChild,    c_WaitChild,    PCT_REG>
  84.         COMMAND    <k_WaitScreen,    c_WaitScreen,    PCT_REG>
  85.         COMMAND    <k_WaitUntil,    c_WaitUntil,    PCT_REG>
  86.         COMMAND    <k_Wipe,    c_Wipe,        PCT_REG>
  87.  
  88. JUMP_INDEX    =    (jump_command - command_table) / SIZE COMMAND
  89. LABEL_INDEX    =    (label_command - command_table) / SIZE COMMAND
  90. SETIF_INDEX    =    (setif_command - command_table) / SIZE COMMAND
  91.  
  92. ; Command keywords
  93.  
  94. command_keys    LABEL    BYTE
  95. k_Break        db    "Break",0
  96. k_Cursor    db    "Cursor",0
  97. k_Else        db    "Else",0
  98. k_EndIf        db    "EndIf",0
  99. k_Flush        db    "Flush",0
  100. k_GetKey    db    "GetKey",0
  101. k_Go        db    "Go",0
  102. k_IfKey        db    "IfKey",0
  103. k_IfLoad    db    "IfLoad",0
  104. k_IfScreen    db    "IfScreen",0
  105. k_Jump        db    "Jump",0
  106. k_Key        db    "Key",0
  107. k_Label        db    "Label",0
  108. k_Load        db    "Load",0
  109. k_Lock        db    "Lock",0
  110. k_Mode        db    "Mode",0
  111. k_Pause        db    "Pause",0
  112. k_Output    db    "Output",0
  113. k_Quit        db    "Quit",0
  114. k_Screen    db    "Screen",0
  115. k_SetIf        db    " SetIf",0        ; cannot be written
  116. k_TypeRate    db    "TypeRate",0
  117. k_Unlock     db    "Unlock",0
  118. k_Video        db    "Video",0
  119. k_WaitChild    db    "WaitChild",0
  120. k_WaitScreen    db    "WaitScreen",0
  121. k_WaitUntil    db    "WaitUntil",0
  122. k_Wipe        db    "Wipe",0
  123.         db    0        ; end of table marker
  124.  
  125. ; Key table for "On"/"Off" arguments:
  126.  
  127. on_off        db    'OFF',0,'ON',0,0    ; Off is 0, On is 1
  128.  
  129. ; Dispatch table for preprocessing commands by type
  130.  
  131. preprocessing_table    LABEL    WORD
  132.         dw    pp_regular, pp_If, pp_Else, pp_EndIf
  133.  
  134. ; Extra dispatch table for conditional commands
  135.  
  136. n_table        dw    n_Nop, n_If, c_Else, c_EndIf
  137.  
  138. ; Miscellaneous stuff
  139.  
  140. pan_extension    db    '.PAN',0    ; Standard extension for Pan scripts
  141. pan_sp        dw    0        ; SP on transferring to a child program
  142. break_condition    db    0        ; ? break on or off
  143. command_ptr    dw    script_buffer
  144. current_command    dw    0        ; pointer to current command in script_buffer
  145. file_handle    dw    ?        ; handle for command file
  146. if_condition    db    0        ; IF condition
  147. if_effect_level    db    0        ; Level at which last If was TRUE
  148. if_nest_level    db    0        ; IF condition level
  149. in_pan_flag    db    0        ; set non-zero when in Pan timer intercept
  150. keyboard_feed    db    0        ; set when PAN needs exclusive access
  151.                     ; to the keyboard
  152. keyboard_state    db    0        ; 0 => unlocked, 1 => locked
  153. kbb_segment    dw    KBB_SEGADD    ; memory segment of keyboard buffer
  154. line_buffer    db    128 dup (?)    ; buffer for reading text through
  155. pan_state    db    PS_INITIAL    ; see list of PS_xxxx states above
  156. screen_columns    db    0        ; number of columns displayed in current video mode
  157. recall_address    dw    0        ; address to recall after timer expiry
  158. time_out    dw    0        ; time_out counter (ticks)
  159. type_rate    dw    0        ; simulation rate for typing
  160. va        db    70h        ; video attribute, default like DOS MDA
  161. video_segment    dw    0        ; memory segment address of video buffer
  162.  
  163. ; Saved BIOS-keyboard interrupt vector
  164.  
  165. i_BIOS_kb    LABEL    dword    
  166. x_bk_offset    dw    0
  167. x_bk_segment    dw    0
  168.  
  169. ; Saved timer interrupt vector
  170.  
  171. i_timer        LABEL    dword
  172. x_timer_offset    dw    0
  173. x_timer_segment    dw    0
  174.  
  175. ; Saved keyboard interrupt vector
  176.  
  177. i_keyboard    LABEL    dword
  178. x_key_offset    dw    0
  179. x_key_segment    dw    0
  180.  
  181. ; Saved Ctrl-Break interrupt vector
  182.  
  183. i_ctrl_break    LABEL    dwORD
  184. x_break_offset    dw    0
  185. x_break_segment    dw    0
  186.  
  187. ; Stack pointer from intercept
  188.  
  189. callers_sp    dw    0
  190. callers_ss    dw    0
  191.  
  192. ; Last keypress obtained by a GetKey command
  193.  
  194. keypress    LABEL    WORD    
  195. key_ASCII    db    0
  196. key_scan    db    0
  197.  
  198. ; Screen position
  199.  
  200. screen_position    LABEL    word
  201. n_col        db    0    ; column number
  202. n_row        db    0    ; row number
  203.  
  204. ; "Keyboard" Input Queue pointers
  205.  
  206. kiq_first    dw    0    ; pointer to first/next character
  207.  
  208. ; Hour and minute for WaitUntil command
  209.  
  210. until_time    LABEL    WORD
  211. minute        db    0    ; minute to wait for (0 - 60)
  212. hour        db    0    ; hour to wait for (0 - 24)
  213.  
  214. ; Parameter block for DOS program-load function
  215.  
  216. parameter_block    LABEL    WORD
  217. env_seg        dw    0    ; segment of environment string
  218. p_command_line    LABEL    dwORD    ; pointer to command line
  219. command_offset    dw    0
  220. command_segment    dw    0
  221. FCB1        LABEL    dwORD    ; FCB pointers
  222. FCB1_O        dw    0
  223. FCB1_S        dw    0
  224. FCB2        LABEL    dwORD
  225. FCB2_O        dw    0
  226. FCB2_S        dw    0
  227. child_sp    dw    0    ; child's SP
  228. child_ss    dw    0    ; child's SS
  229. child_ip    dw    0    ; child's IP
  230. child_cs    dw    0    ; child's CS
  231.  
  232. ; Other information about the child process
  233.  
  234. child_psp    dw    0    ; segment of child's PSP
  235. child_size    dw    0    ; size in paragraphs
  236.  
  237. ; Video mode table
  238.  
  239. vseg_table    LABEL    BYTE            ; Mode    Type
  240.         db    0B8h        ; 0:  CGA 40x25 b/w
  241.         db    0B8h        ; 1:  CGA 40x25 16 colors
  242.         db    0B8h        ; 2:  CGA 80x25 b/w
  243.         db    0B8h        ; 3:  CGA 80x25 16 colors
  244.         db    0        ; 4:  CGA graphics mode
  245.         db    0        ; 5:  CGA graphics mode
  246.         db    0        ; 6:  CGA graphics mode
  247.         db    0B0h        ; 7:  MDA 80x25 b/w
  248.  
  249. ; Translation table:  ASCII codes into keyboard scan codes
  250.  
  251. scan    db    03, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24
  252. ;              Nul  ^A  ^B  ^C  ^D  ^E  ^F  ^G  ^H  ^I  ^J  ^K  ^L  ^M  ^N  ^O
  253.     db    25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 01, 26, 53, 27, 12
  254. ;        ^P  ^Q  ^R  ^S  ^T  ^U  ^V  ^W  ^X  ^Y  ^Z Esc  FS  GS  RS  US
  255.     db    57, 02, 40, 04, 05, 06, 08, 40, 10, 11, 09, 13, 51, 12, 52, 53
  256. ;        sp   !   "   #   $   %   &   '   (   )   *   +   ,   -   .   /
  257.     db    11, 02, 03, 04, 05, 06, 07, 08, 09, 10, 39, 39, 51, 13, 52, 53
  258. ;         0   1   2   3   4   5   6   7   8   9   :   ;   <   =   >   ?
  259.     db    03, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24
  260. ;         @   A   B   C   D   E   F   G   H   I   J   K   L   M   N   O
  261.     db    25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 26, 43, 27, 07, 12
  262. ;         P   Q   R   S   T   U   V   W   X   Y   Z   [   \   ]   ^   _
  263.     db    41, 30, 48, 46, 32, 18, 33, 34, 35, 23, 36, 37, 38, 50, 49, 24
  264. ;         `   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o
  265.     db    25, 16, 19, 31, 20, 22, 47, 17, 45, 21, 44, 26, 43, 27, 41, 14
  266. ;         p   q   r   s   t   u   v   w   x   y   z   {   |   }   ~ Del
  267.  
  268. ; Translation table for special keys
  269.  
  270. keyname_list    LABEL    BYTE
  271.     db    'ESC',0,'TAB',0,'ENTER',0
  272.     db    'F1',0,'F2',0,'F3',0,'F4',0,'F5',0,'F6',0,'F7',0,'F8',0,'F9',0
  273.     db    'F10',0
  274.     db    'HOME',0,'UP',0,'PGUP',0,'LEFT',0
  275.     db    'RIGHT',0,'END',0,'DOWN',0,'PGDN',0,'INS',0,'DEL',0
  276.     db    0
  277.  
  278. shiftname_list    LABEL    BYTE
  279.     db    'ALT',0,'CTRL',0,'SHIFT',0,0
  280.  
  281. shiftbits    LABEL    BYTE
  282.     db    08h, 04h, 02h
  283.  
  284. key_scans    LABEL    BYTE
  285.     db    1    ; Escape
  286.     db    15    ; Tab
  287.     db    28    ; Enter
  288.     db    59,60,61,62,63,64,65,66,67,68    ; F1 - F10
  289.     db    71    ; Home
  290.     db    72    ; Up Arrow
  291.     db    73    ; Page Up
  292.     db    75    ; Left Arrow
  293.     db    77    ; Right Arrow
  294.     db    79    ; End
  295.     db    80    ; Down Arrow
  296.     db    81    ; Page Down
  297.     db    82    ; Insert
  298.     db    83    ; Delete
  299.  
  300. ; Shift tables
  301.  
  302. No_shift    LABEL    WORD
  303.     dw    0000h, 011Bh, 0231h, 0332h, 0433h, 0534h, 0635h, 0736h
  304.     dw    0837h, 0938h, 0A39h, 0B30h, 0C2Dh, 0D3Dh, 0E08h, 0F09h
  305.     dw    1071h, 1177h, 1265h, 1372h, 1474h, 1579h, 1675h, 1769h
  306.     dw    186Fh, 1970h, 1A5Bh, 1B5Dh, 1C0Dh, 0000h, 1E61h, 1F73h
  307.     dw    2064h, 2166h, 2267h, 2368h, 246Ah, 256Bh, 266Ch, 273Bh
  308.     dw    2827h, 2960h, 0000h, 2B5Ch, 2C7Ah, 2D78h, 2E63h, 2F76h
  309.     dw    3062h, 316Eh, 326Dh, 332Ch, 342Eh, 352Fh, 0000h, 372Ah
  310.     dw    0000h, 3920h, 0000h, 3B00h, 3C00h, 3D00h, 3E00h, 3F00h
  311.     dw    4000h, 4100h, 4200h, 4300h, 4400h, 0000h, 0000h, 4700h
  312.     dw    4800h, 4900h, 4A2Dh, 4B00h, 0000h, 4D00h, 4E2Bh, 4F00h
  313.     dw    5000h, 5100h, 5200h, 5300h
  314.  
  315. Shift_shift    LABEL    WORD
  316.     dw    0000h, 011Bh, 0221h, 0340h, 0423h, 0524h, 0625h, 075Eh
  317.     dw    0826h, 092Ah, 0A28h, 0B29h, 0C5Fh, 0D2Bh, 0E08h, 0F00h
  318.     dw    1051h, 1157h, 1245h, 1352h, 1454h, 1559h, 1655h, 1749h
  319.     dw    184Fh, 1950h, 1A7Bh, 1B7Dh, 1C0Dh, 0000h, 1E41h, 1F53h
  320.     dw    2044h, 2146h, 2247h, 2348h, 244Ah, 254Bh, 264Ch, 273Ah
  321.     dw    2822h, 297Eh, 0000h, 2B7Ch, 2C5Ah, 2D58h, 2E43h, 2F56h
  322.     dw    3042h, 314Eh, 324Dh, 333Ch, 343Eh, 353Fh, 0000h, 0000h
  323.     dw    0000h, 3920h, 0000h, 5400h, 5500h, 5600h, 5700h, 5800h
  324.     dw    5900h, 5A00h, 5B00h, 5C00h, 5D00h, 0000h, 0000h, 4737h
  325.     dw    4838h, 4939h, 4A2Dh, 4B34h, 4C35h, 4D36h, 4E2Bh, 4F31h
  326.     dw    5032h, 5133h, 5230h, 532Eh
  327.  
  328. Ctrl_shift    LABEL    WORD
  329.     dw    0000h, 011Bh, 0000h, 0300h, 0000h, 0000h, 0000h, 071Eh
  330.     dw    0000h, 0000h, 0000h, 0000h, 0C1Fh, 0000h, 0E7Fh, 0000h
  331.     dw    1011h, 1117h, 1205h, 1312h, 1414h, 1519h, 1615h, 1709h
  332.     dw    180Fh, 1910h, 1A1Bh, 1B1Dh, 1C0Ah, 0000h, 1E01h, 1F13h
  333.     dw    2004h, 2106h, 2207h, 2308h, 240Ah, 250Bh, 260Ch, 0000h
  334.     dw    0000h, 0000h, 0000h, 2B1Ch, 2C1Ah, 2D18h, 2E03h, 2F16h
  335.     dw    3002h, 310Eh, 320Dh, 0000h, 0000h, 0000h, 0000h, 3710h
  336.     dw    0000h, 3920h, 0000h, 5E00h, 5F00h, 6000h, 6100h, 6200h
  337.     dw    6300h, 6400h, 6500h, 6600h, 6700h, 0000h, 0000h, 7700h
  338.     dw    0000h, 8400h, 0000h, 7300h, 0000h, 7400h, 0000h, 7500h
  339.     dw    0000h, 7600h, 0000h, 0000h
  340.  
  341. Alt_shift    LABEL    WORD
  342.     dw    0000h, 0000h, 7800h, 7900h, 7A00h, 7B00h, 7C00h, 7D00h
  343.     dw    7E00h, 7F00h, 8000h, 8100h, 8200h, 8300h, 0000h, 0000h
  344.     dw    1000h, 1100h, 1200h, 1300h, 1400h, 1500h, 1600h, 1700h
  345.     dw    1800h, 1900h, 0000h, 0000h, 0000h, 0000h, 1E00h, 1F00h
  346.     dw    2000h, 2100h, 2200h, 2300h, 2400h, 2500h, 2600h, 0000h
  347.     dw    0000h, 0000h, 0000h, 0000h, 2C00h, 2D00h, 2E00h, 2F00h
  348.     dw    3000h, 3100h, 3200h, 0000h, 0000h, 0000h, 0000h, 0000h
  349.     dw    0000h, 3920h, 0000h, 6800h, 6900h, 6A00h, 6B00h, 6C00h
  350.     dw    6D00h, 6E00h, 6F00h, 7000h, 7100h, 0000h, 0000h, 0000h
  351.     dw    0000h, 0000h, 0000h, 0000h, 0000h, 0000h, 0000h, 0000h
  352.     dw    0000h, 0000h, 0000h, 0000h
  353.  
  354. ;******************************************************************************
  355. ;*                                                                            *
  356. ;*                     Interrupt-Intercept Procedures                         *
  357. ;*                                                                            *
  358. ;******************************************************************************
  359.  
  360. ;    timer-interrupt intercept
  361.  
  362. timer_intercept    proc    far
  363.     pushf                ; simulate another interrupt
  364.     call    cs:i_timer        ;    to let timer do its thing
  365.     push    ax
  366.     mov    al,1
  367.     xchg    al,cs:in_pan_flag    ; check we're not already here
  368.     or    al,al
  369.     jnz    .tim4            ; exit immediately if so
  370.  
  371.     mov    ax,sp            ; switch stacks
  372.     mov    cs:callers_sp,sp
  373.     mov    ax,ss
  374.     mov    cs:callers_ss,ax
  375.     mov    ax,cs
  376.     mov    ss,ax
  377.     mov    sp,OFFSET interrupt_stack
  378.     sti                ; allow interrupts
  379.  
  380.     push    bx            ; save all registers
  381.     push    cx
  382.     push    dx
  383.     push    si
  384.     push    di
  385.     push    ds
  386.     push    es
  387.     push    bp
  388.     mov    ax,cs            ; set DS and ES to PAN segment
  389.     mov    ds,ax
  390.     mov    es,ax
  391.     cld
  392.  
  393.     mov    ax,time_out        ; AX = number of ticks to timeout
  394.     test    ax,ax            ; are we in a waiting period?
  395.     jz    .tim2            ; if no waiting
  396.     dec    time_out        ; else count down the ticks
  397.     jnz    .tim3            ; if more to go
  398.     call    [recall_address]    ; recall processor for current command
  399.     jmp    SHORT .tim3
  400.  
  401. .tim2:    call    interpret        ; process a new command
  402.     cmp    time_out,0        ; check number of ticks to timeout
  403.     je    .tim2            ; if no wait then do another
  404.  
  405. .tim3:    pop    bp            ; restore state
  406.     pop    es
  407.     pop    ds
  408.     pop    di
  409.     pop    si
  410.     pop    dx
  411.     pop    cx
  412.     pop    bx
  413.  
  414.     cli                ; turn off interrupts
  415.     mov    ax,cs:callers_sp    ; restore the interruptee's stack
  416.     mov    sp,ax
  417.     mov    ax,cs:callers_ss
  418.     mov    ss,ax
  419.     mov    cs:in_pan_flag,0    ; and reset in-Pan flag
  420.  
  421. .tim4:    pop    ax
  422.     iret
  423. timer_intercept    endp
  424.  
  425. ;     Keyboard interrupt intercept.  Every time a keyboard interrupt
  426. ;    occurs we mess with the pointers to make it seem that the BIOS
  427. ;    keyboard-input queue is full.  This allows a Ctrl-Alt-Del to
  428. ;    take effect but for all normal keypresses the user will get a
  429. ;    beep.
  430.  
  431. keyboard_intercept    proc    far
  432.     push    ax
  433.     push    ds
  434.     mov    ds,cs:kbb_segment    ; DS = keyboard-buffer segment
  435.     mov    ax,ds:[KBB_TAIL]    ; get tail
  436.     inc    ax            ; bump tail pointer
  437.     inc    ax
  438.     cmp    ax,ds:[KBB_END]
  439.     jne    .ki1
  440.     mov    ax,ds:[KBB_START]    ; if wrapped around
  441.  
  442. .ki1:    xchg    ax,ds:[KBB_HEAD]    ; make it look like there's no room
  443.     pushf                ; fake interrupt to real handler
  444.     call    cs:i_keyboard
  445.     xchg    ax,ds:[KBB_HEAD]    ; replace "real" head of queue
  446.     pop    ds
  447.     pop    ax
  448.     iret                ; disconnects the keyboard
  449. keyboard_intercept    endp
  450.  
  451. ;    BIOS-keyboard interrupt intercept
  452.  
  453. BIOS_kb_intercept    proc    far
  454.     pushf
  455.     cmp    ah,01h            ; Function 0 or 1?
  456.     ja    .kbi2            ; no, let BIOS handle it
  457.     sti                ; ensure interrupts can happen
  458.     je    .kbi1
  459.  
  460. ; Handle function 00h:  Read Character from Keyboard.  If PAN has locked the
  461. ; keyboard then we delay the process until the lock is released.  If PAN has
  462. ; not locked the keyboard then we check if a character is available; if it is
  463. ; then we let the BIOS complete the request, else keep waiting in case PAN
  464. ; locks the keyboard.
  465.  
  466. .kbi0:    test    cs:keyboard_feed,0FFh    ; has PAN reserved the keyboard?
  467.     jnz    .kbi0
  468.  
  469.     mov    ah,01h            ; BIOS Get Keyboard Status
  470.     pushf
  471.     call    cs:[i_BIOS_kb]
  472.     jz    .kbi0
  473.     mov    ah,00h
  474.     jmp    SHORT .kbi2
  475.  
  476. ; Handle function 01h:  Get Keyboard Status.  If PAN has locked the keyboard
  477. ; then we return a no-character-waiting indication to the process.  If PAN
  478. ; has not locked the keyboard then we let the BIOS handle the request.
  479.  
  480. .kbi1:    test    cs:keyboard_feed,0FFh    ; has PAN reserved the keyboard?
  481.     jz    .kbi2            ; no, go to BIOS
  482.     popf
  483.     xor    ax,ax            ; yes, return with no input indication
  484.     retf    2
  485.  
  486. .kbi2:    popf
  487.     jmp    cs:[i_BIOS_kb]
  488. BIOS_kb_intercept    endp
  489.  
  490. ; Ctrl-Break intercept
  491.  
  492. ctrl_break_intercept    proc    far
  493.     iret
  494. ctrl_break_intercept    endp
  495.  
  496. ;******************************************************************************
  497. ;*                                                                            *
  498. ;*                               Entry Code                                   *
  499. ;*                                                                            *
  500. ;******************************************************************************
  501.  
  502.     assume    ds:code
  503. main    proc    near
  504.     cld
  505.     mov    sp,100h            ; set internal stack
  506.  
  507.     mov    dx,OFFSET initmsg    ; announce program
  508.     mov    ah,9h
  509.     int    21h
  510.     call    c_Mode            ; determine video mode
  511.     push    es
  512.     mov    ax,3516h        ; get interrupt vector for BIOS kb
  513.     int    21h            ; ES:BX -> BIOS kb service
  514.     mov    x_bk_offset,bx        ; save this for internal use
  515.     mov    ax,es
  516.     mov    x_bk_segment,ax
  517.     pop    es
  518.  
  519.     call    get_script        ; load the command file
  520.     jc    .mai3
  521.     mov    bx,OFFSET script_buffer    ; calculate paragraphs used
  522.     add    bx,ax            ; AX = size of script as loaded
  523.     add    bx,15            ; round up to a paragraph boundary
  524.     mov    cx,4
  525.     shr    bx,cl            ; convert to paragraphs
  526.     mov    ah,4Ah            ; DOS modify allocated memory blocks
  527.     int    21h
  528.     call    resolve_jumps        ; prepare the script
  529.     jc    .mai3            ; if an error was detected
  530.     mov    ax,OFFSET script_buffer    ; set command pointer
  531.     mov    command_ptr,ax
  532.  
  533. .mai1:    call    interpret        ; perform the first/next command
  534.  
  535. .mai2:    xor    cx,cx
  536.     xchg    cx,time_out        ; CX = timeout
  537.     test    cx,cx            ; did last command set a timeout?
  538.     jz    .mai1            ; if not continue processing
  539.     call    delay            ; else delay for the requisite period
  540.     call    [recall_address]    ; then call the completion code
  541.     jmp    SHORT .mai2        ; which can timeout again
  542.  
  543. .mai3:    mov    ah,9h            ; get here with SI -> error message
  544.     int    21h            ; have DOS display it
  545.  
  546. .mai4:    jmp    terminate        ; die
  547. main    endp
  548.  
  549. ;******************************************************************************
  550. ;*                                                                            *
  551. ;*                    Primary PAN Command Interpreter                         *
  552. ;*                                                                            *
  553. ;******************************************************************************
  554.  
  555. interpret    proc    near
  556.     mov    si,command_ptr        ; SI -> next command
  557.     xor    ax,ax
  558.     lodsb                ; AX = command length
  559.     test    al,al            ; zero-length command => end of script
  560.     jz    .int3
  561.     add    command_ptr,ax        ; update the command pointer
  562.     mov    current_command,si    ; save pointer to current command
  563.     lodsb                ; AX = command index
  564.     mul    command_entry_size    ; convert to table offset
  565.     mov    bx,ax            ; BX = entry offset
  566.     xor    ax,ax
  567.     or    al,if_condition
  568.     jnz    .int1            ; if processing off
  569.                 ; call the command processor with AX = 0
  570.     call    WORD PTR [command_table+PC_PROC+bx]
  571.     mov    time_out,ax        ; store time-out counter
  572.     mov    recall_address,bx    ; and recall address if valid
  573.     ret
  574.  
  575. .int1:    xor    ax,ax            ; get AX = command type
  576.     mov    al,BYTE PTR [command_table+PC_TYPE+bx]
  577.     mov    bx,ax            ; and call corresponding proc
  578.     call    [n_table+bx]
  579.  
  580. .int2:    ret
  581.  
  582. .int3:    jmp    c_Quit            ; Quit on end of script
  583. interpret    endp
  584.  
  585. ;******************************************************************************
  586. ;*                                                                            *
  587. ;*                 Procedures for performing PAN commands                     *
  588. ;*                                                                            *
  589. ;******************************************************************************
  590.  
  591. ;    Break On/Off
  592.  
  593. c_Break    proc    near
  594.     mov    bx,OFFSET on_off    ; BX -> "ON/OFF"
  595.     call    match_key        ; check the argument
  596.     jne    .cb1            ; if not "ON" nor "OFF"
  597.     mov    break_condition,al    ; else index sets the break condition
  598.     xor    ax,ax            ; this command complete
  599.     ret
  600.  
  601. .cb1:    mov    si,OFFSET .cbmsg    ; "Break should have argument On or Off"
  602.     jmp    command_error
  603.  
  604. .cbmsg    db    'Break should have argument "On" or "Off"',0
  605. c_Break    endp
  606.  
  607. ;    Else - if Else belongs to the last If processed then reverse the
  608. ;           current if condition
  609.  
  610. c_Else    proc    near
  611.     mov    al,if_nest_level    ; is this else effective?
  612.     cmp    al,if_effect_level
  613.     ja    .cel1            ; ignore if not
  614.     not    if_condition        ; switch the condition marker
  615.  
  616. .cel1:    xor    ax,ax            ; this command completed
  617.     ret
  618. c_Else    endp
  619.  
  620. ;    Cursor <row> <column> - move the cursor to the given position.
  621.  
  622. c_Cursor    proc    near
  623.     call    get_screen_position    ; decode row and column
  624.     mov    ah,02h            ; BIOS Set Cursor Position
  625.     xor    bx,bx            ; assume page 0
  626.     mov    dx,screen_position    ; DH = row, DL = column
  627.     int    10h
  628.     xor    ax,ax            ; that does it
  629.     ret
  630. c_Cursor    endp
  631.  
  632. ;    EndIf - terminate an IF clause
  633.  
  634. c_EndIf    proc    near
  635.     cmp    if_nest_level,0        ; is EndIf appropriate?
  636.     jz    .cen1            ; ignore if not (should be impossible)
  637.     mov    al,if_nest_level    ; if this EndIf effective?
  638.     dec    if_nest_level        ; count out one level
  639.     cmp    al,if_effect_level
  640.     jne    .cen1            ; if not there is no more to do
  641.     dec    if_effect_level
  642.     mov    if_condition,0        ; process!
  643.  
  644. .cen1:    xor    ax,ax            ; all done
  645.     ret
  646. c_EndIf    endp
  647.  
  648. ;    Flush - flush keypress buffer
  649.  
  650. c_Flush    proc    near
  651.  
  652. .cf1:    mov    ah,01h            ; check for keyboard input
  653.     pushf                ; by emulating interrupt to the BIOS
  654.     call    [i_BIOS_kb]        ; int    16h
  655.     jz    .cf2            ; if no input
  656.     xor    ax,ax            ; else read that input
  657.     pushf
  658.     call    [i_BIOS_kb]        ; int    16h
  659.     jmp    SHORT .cf1        ; keep checking until there is none
  660.  
  661. .cf2:    xor    ax,ax            ; no continuation
  662.     ret
  663. c_Flush    endp
  664.  
  665. ;    GetKey - input a keypress
  666.  
  667. c_GetKey    proc    near
  668.     cmp    pan_state,PS_RUNNING    ; target program in action?
  669.     je    .gk3            ; yes, get a keypress by stealth
  670.     mov    ah,00h            ; else just use BIOS service
  671.     int    16h
  672.     mov    keypress,ax        ; save the codes
  673.     cmp    break_condition,0    ; break mode on?
  674.     jz    .gk1            ; no, don't handle aborts specially
  675.     cmp    ax,2E03h        ; Control-C?
  676.     je    .gk2            ; quit if so
  677.  
  678. .gk1:    xor    ax,ax
  679.     ret
  680.  
  681. .gk2:    jmp    c_Quit
  682.  
  683. .gk3:    mov    ax,1            ; check on every tick
  684.     mov    bx,OFFSET .gk4        ; come back at label .gk4
  685.     inc    keyboard_feed        ; lock the keyboard
  686.     ret
  687.  
  688. .gk4:    mov    ah,01h            ; check for keyboard input
  689.     pushf
  690.     call    [i_BIOS_kb]        ; int    16h
  691.     jz    .gk5            ; if none
  692.     xor    ax,ax            ; read that input
  693.     pushf
  694.     call    [i_BIOS_kb]        ; int    16h
  695.     mov    keypress,ax        ; save it
  696.     dec    keyboard_feed        ; release the keyboard
  697.     ret
  698.  
  699. .gk5:    inc    time_out        ; continue waiting
  700.     ret
  701. c_GetKey    endp
  702.  
  703. ;    Go - initiate execution of a loaded program
  704.  
  705. c_Go    proc    near
  706.     cmp    pan_state,PS_LOADED    ; check that state is correct
  707.     je    .go2            ; if okay...
  708.     mov    si,OFFSET .gomsg2    ; "Program already running"
  709.     jg    .go1            ; error if Go done already
  710.     mov    si,OFFSET .gomsg1    ; "No program loaded"
  711.  
  712. .go1:    jmp    command_error
  713.  
  714. .gomsg1    db    'No program loaded',0
  715. .gomsg2    db    'Program already running',0
  716.  
  717. ; Copy command line to child's PSP
  718.  
  719. .go2:    call    normalize        ; copy command line
  720.     mov    es,child_psp        ; ES = PSP of child
  721.     mov    di,81h            ; ES:DI -> command-line area
  722.     mov    al,' '            ; force a blank at the start
  723.     cmp    [si],al
  724.     je    .go3
  725.     stosb                ; good command lines start this way
  726.  
  727. .go3:    rep    movsb            ; copy command line
  728.     dec    di            ; and append a carriage return
  729.     mov    BYTE PTR es:[di],CR
  730.     mov    ax,di            ; calculate length of command line
  731.     sub    al,81h
  732.     mov    es:[80h],al        ; and prepend length to the line
  733.  
  734. ; Set up default FCBs just in case
  735.  
  736.     push    ds
  737.     mov    ax,2901h        ; DOS Parse filename
  738.     mov    ds,child_psp
  739.     mov    si,81h            ; DS:SI -> command line to parse
  740.     mov    di,92            ; ES:DI -> place for 1st FCB
  741.     int    21h
  742.     mov    cx,ax            ; save drive valid flag
  743.     mov    ax,2901h        ; DOS Parse filename
  744.     mov    di,108            ; ES:DI -> place for 2nd FCB
  745.     int    21h
  746.     pop    ds
  747.  
  748.     mov    in_pan_flag,1        ; make intercepts ineffective
  749.     call    set_traps        ; set traps
  750.     mov    pan_state,PS_RUNNING    ; set state to running
  751.     jmp    run_it            ; and transfer control
  752. c_Go    endp
  753.  
  754. ;    IfKey "keylist" - check if last captured keystroke is in the given list.
  755.  
  756. c_IfKey    proc    near
  757.     call    normalize        ; copy and fix the string
  758.  
  759. .ifk1:    call    translate        ; get AX = key code
  760.     jc    iffalse            ; if no more keys in string
  761.     cmp    ax,keypress        ; is it what we captured?
  762.     je    iftrue
  763.     jne    .ifk1
  764. c_IfKey    endp
  765.  
  766. ;    IfLoad "program_name" - attempt to load the specified program and
  767. ;                set condition code according to result
  768.  
  769. c_IfLoad    proc    near
  770.     cmp    pan_state,PS_INITIAL    ; check that state is suitable
  771.     jne    .ifl1            ; if a program has already been loaded
  772.     call    loader            ; try the load
  773.     jc    iffalse            ; if load failed
  774.     jnc    iftrue
  775.  
  776. .ifl1:    mov    si,OFFSET .loadm    ; complain, complain, complain
  777.     jmp    command_error
  778.  
  779. .loadm    db    'A program is already loaded',0
  780. c_IfLoad    endp
  781.  
  782. ;    IfScreen <row> <column> "string" - check if "string" appears on screen
  783.  
  784. c_IfScreen    proc    near
  785.     call    get_screen_position    ; decode row and column
  786.     call    skip_whitespace        ; find the "string"
  787.     call    normalize        ; copy and normalize the string
  788.     call    check_screen        ; check if it's there
  789.     jns    iftrue            ; if the string is there
  790.     js    iffalse            ; if it's not
  791. c_IfScreen    endp
  792.  
  793. ;    Set If condition false
  794.  
  795. iffalse    proc    near
  796.     not    if_condition        ; inhibit processing
  797.                     ; and fall through
  798. iffalse    endp
  799.  
  800. ;    Set If condition true
  801.  
  802. iftrue    proc    near
  803.     inc    if_nest_level        ; count up one more If level
  804.     inc    if_effect_level        ; and active level
  805.     xor    ax,ax            ; and we're done
  806.     ret    
  807. iftrue    endp
  808.  
  809. ;    Jump label - transfer control to command following the named label.
  810.  
  811. c_Jump    proc    near
  812.     lodsw                ; AX -> destination
  813.     mov    command_ptr,ax        ; set new command pointer
  814.     xor    ax,ax            ; done
  815.     ret
  816. c_Jump    endp
  817.  
  818. ;    Key "string" - make it appear as though "string" were typed.
  819.  
  820. c_Key    proc    near
  821.     call    copy_string        ; copy the string
  822.     mov    kiq_first,si        ; point to first character
  823.     mov    ax,1            ; continue on next tick
  824.     mov    bx,OFFSET stuff_keys    ; at proc stuff_keys
  825.     ret
  826. c_Key    endp
  827.  
  828. ;    Label name
  829.  
  830. c_Label    proc    near
  831.     ret                ; no operation
  832. c_Label    endp
  833.  
  834. ;    Load "program_name"
  835.  
  836. c_Load    proc    near
  837.     cmp    pan_state,PS_INITIAL    ; check that state is suitable
  838.     jne    .ifl1            ; if a program has already been loaded
  839.     call    loader            ; attempt a load
  840.     jc    bad_load        ; if load failed
  841.  
  842. .cl1:    xor    ax,ax            ; load successful, continue
  843.     ret
  844.  
  845. bad_load:
  846.     mov    dx,OFFSET .clA        ; "Cannot find target program"
  847.     cmp    al,3            ; file or path not found?
  848.     jle    .bl1
  849.     mov    dx,OFFSET .clB        ; "Insufficient memory to load"
  850.     cmp    al,8
  851.     je    .bl1
  852.     mov    dx,OFFSET .clC        ; "Cannot load target program"
  853.  
  854. .bl1:    mov    ah,9h
  855.     int    21h
  856.     call    ttyz            ; display filename
  857.     jmp    c_Quit
  858.  
  859. .clA    db    'PAN Error:  Cannot find target program:  $'
  860. .clB    db    'PAN Error:  Insufficient memory to load program:  $'
  861. .clC    db    'PAN Error:  Cannot load target program:  $'
  862. c_Load    endp
  863.  
  864. ;    Lock - disconnect keyboard from application
  865.  
  866. c_Lock    proc    near
  867.     cmp    keyboard_state,0    ; is keyboard already locked?
  868.     jne    .loc1            ; if so this is a no-op
  869.     inc    keyboard_state        ; else set state to locked
  870.     mov    dx,OFFSET keyboard_intercept    ; replace keyboard interrupt
  871.     mov    al,9h
  872.     mov    bx,OFFSET i_keyboard
  873.     call    set_vector
  874.  
  875.     mov    dx,OFFSET ctrl_break_intercept    ; replace Ctrl-Break interrupt
  876.     mov    al,23h
  877.     mov    bx,OFFSET i_ctrl_break
  878.     call    set_vector
  879.  
  880. .loc1:    xor    ax,ax
  881.     ret
  882. c_Lock    endp
  883.  
  884. ;    Mode - force reassessment of current video mode
  885.  
  886. c_Mode    proc    near
  887.     mov    ah,0Fh            ; BIOS Get Video Mode
  888.     int    10h            ; returns AH = # columns, AL = mode,
  889.                     ;    and BH = active page
  890.     cmp    al,7            ; we only do text modes (0,1,2,3 and 7)
  891.     ja    .cm1
  892.     mov    screen_columns,ah    ; save number of columns on screen
  893.     xor    ah,ah
  894.     mov    bx,ax            ; BX = mode
  895.     xor    ax,ax
  896.     mov    ah,[vseg_table+bx]    ; AX = video buffer segment
  897.     mov    video_segment,ax
  898.     ret
  899.  
  900. .cm1:    mov    video_segment,0        ; video mode that PAN does not handle
  901.     ret
  902. c_Mode    endp
  903.  
  904. ;    Output <string> - send a string to standard output
  905.  
  906. c_Output    proc    near
  907.     cmp    pan_state,PS_RUNNING    ; is state suitable for DOS call?
  908.     jae    .co2            ; ignore the command if it's not
  909.     call    normalize        ; straighten up the string
  910.  
  911. .co1:    lodsb                ; AL = next character
  912.     test    al,al
  913.     jz    .co2            ; at end of string
  914.     mov    ah,02h            ; DOS Display Output
  915.     mov    dl,al            ; DL = character
  916.     int    21h
  917.     jmp    SHORT .co1        ; loop for all characters
  918.  
  919. .co2:    xor    ax,ax            ; and we're done
  920.     ret
  921. c_Output    endp
  922.  
  923. ;    Pause <n> ticks/seconds/minutes - delay for a given period
  924.  
  925. c_Pause    proc    near
  926.     call    decode_decimal        ; decode decimal count
  927.     test    ah,ah            ; 0 - 255 allowed
  928.     jnz    .cp2            ; if out of bounds
  929.     mov    cx,ax            ; CX = number
  930.     call    skip_whitespace        ; skip to units
  931.     jz    .cps            ; if no units then use seconds
  932.     call    isletter        ; check that units starts with a letter
  933.     jc    .cp2            ; give error if it doesn't
  934.     cmp    al,'T'            ; ticks?
  935.     je    .cpt
  936.     cmp    al,'S'            ; seconds?
  937.     je    .cps
  938.     cmp    al,'M'            ; minutes?
  939.     jne    .cp2
  940.     cmp    cl,60            ; 60 minutes is the max
  941.     jg    .cp2
  942.     mov    ax,1092            ; AX = number of ticks in a minute
  943.     mul    cx            ; get AX = number of seconds
  944.     jmp    SHORT .cp0
  945.  
  946. .cps:    mov    al,18            ; multiple by 18.25 to approximate 18.2
  947.     mul    cl
  948.     shr    cx,1
  949.     shr    cx,1
  950.     add    ax,cx
  951.     jmp    SHORT .cp0
  952.  
  953. .cpt:    mov    ax,cx            ; AX = tick count
  954.  
  955. .cp0:    mov    bx,OFFSET .cp1        ; return with AX = timeout, recall here
  956.  
  957. .cp1:    ret
  958.  
  959. .cp2:    mov    si,OFFSET .cpmsg    ; 'Pause 1-255 ticks, 1-255 seconds or 1-60 minutes'
  960.     jmp    command_error
  961.  
  962. five    db    5
  963. .cpmsg    db    'Pause 1-255 ticks, 1-255 seconds or 1-60 minutes',0
  964. c_Pause    endp
  965.  
  966. ;    Quit
  967.  
  968. c_Quit    proc    near
  969.     call    unset_traps        ; make sure no traps are left set
  970.     call    c_Unlock        ; and that the keyboard is unlocked
  971.     cmp    pan_state,PS_LOADED    ; got a program loaded and ready to go?
  972.     je    .cq2            ; if so we must get rid of it
  973.     cmp    pan_state,PS_RUNNING    ; running a child program?
  974.     je    .cq1            ; if so
  975.     jmp    terminate        ; otherwise we can exit gracefully
  976.  
  977. .cq1:    mov    pan_state,PS_QUIT    ; quit when target program quits
  978.     mov    ax,1
  979.     ret
  980.  
  981. .cq2:    mov    child_cs,cs        ; fix things so child will die at birth
  982.     mov    ax,OFFSET terminate
  983.     mov    child_ip,ax
  984.     mov    pan_state,PS_QUIT    ; quit when target program quits
  985.     jmp    run_it            ; then go run it
  986. c_Quit    endp
  987.  
  988. ;    Screen <row> <column>  "string" - write a string directly onto the
  989. ;                      screen.
  990.  
  991. c_Screen    proc    near
  992.     call    get_screen_position    ; decode row and column
  993.     call    skip_whitespace        ; skip to "string"
  994.     call    normalize        ; copy and fix the string
  995.     mov    dx,screen_position    ; DX = row + column
  996.     mov    bl,va            ; BL = video attribute
  997.     call    display_string        ; display the string
  998.     xor    ax,ax            ; no continuation
  999.     ret
  1000. c_Screen    endp
  1001.  
  1002. ;    SetIf - set the if nesting level after a Label.
  1003. ;
  1004. ; Note:  This is an internal command that is inserted automatically
  1005. ;        following each Label command.  The effect is to make Ifs and
  1006. ;        EndIfs work like proper bracket operators.  It allows Jumps
  1007. ;        to be made out of If/EndIf blocks.  It also, er, allows Jumps
  1008. ;        to be made into If/EndIf blocks!!
  1009.  
  1010. c_SetIf    proc    near
  1011.     lodsb                ; AL = current level
  1012.     mov    if_nest_level,al    ; make that the nesting level
  1013.     mov    if_effect_level,al    ; and the effective level
  1014.     xor    ax,ax            ; that's it
  1015.     ret
  1016. c_SetIf    endp
  1017.  
  1018. ;    TypeRate <ticks> - set a rate for emulating typing (in ticks)
  1019.  
  1020. c_TypeRate    proc    near
  1021.     call    decode_decimal        ; decode decimal tick count
  1022.     mov    type_rate,ax        ; store the type rate
  1023.     xor    ax,ax            ; no continuation
  1024.     ret
  1025. c_TypeRate    endp
  1026.  
  1027. ;    Unlock - connect keyboard to application
  1028.  
  1029. c_Unlock    proc    near
  1030.     cmp    keyboard_state,1    ; is keyboard locked?
  1031.     jne    .unl1            ; if not this is a no-op
  1032.     dec    keyboard_state        ; set state to unlocked
  1033.     mov    al,9h            ; remove keyboard intercept
  1034.     mov    bx,OFFSET i_keyboard
  1035.     call    restore_vector
  1036.     mov    al,23h            ; reset Control-Break vector
  1037.     mov    bx,OFFSET i_ctrl_break
  1038.     call    restore_vector
  1039.  
  1040. .unl1:    xor    ax,ax
  1041.     ret
  1042. c_Unlock    endp
  1043.  
  1044. ;    Video <attribute> - set video attribute.
  1045.  
  1046. c_Video    proc    near
  1047.     call    decode_hex        ; decode attribute into AL
  1048.     mov    va,al            ; and store it away
  1049.     xor    ax,ax            ; no continuation
  1050.     ret
  1051. c_Video    endp
  1052.  
  1053. ;    WaitChild - wait for child to die.
  1054.  
  1055. c_WaitChild    proc    near
  1056.     cmp    pan_state,PS_RUNNING    ; only valid in running state
  1057.     jne    .wc2            ; error in any other state
  1058.     mov    pan_state,PS_OBIT    ; change state to PS_OBIT
  1059.     call    unset_traps        ; no longer need these
  1060.  
  1061. .wc1:    mov    ax,1            ; to stop command processing
  1062.     ret
  1063.  
  1064. .wc2:    mov    si,OFFSET .wcA        ; what is PAN expected to do?
  1065.     jmp    command_error
  1066.  
  1067. .wcA    db    'No program running to wait for',0
  1068. c_WaitChild    endp
  1069.  
  1070. ;    WaitScreen <row> <column> "string" - wait for the given string to
  1071. ;                         appear on screen.
  1072.  
  1073. c_WaitScreen    proc    near
  1074.     call    get_screen_position    ; decode row and column
  1075.     call    skip_whitespace        ; skip to the "string"
  1076.     call    normalize        ; copy and normalize the string
  1077.     mov    ax,1            ; check on next tick
  1078.     mov    bx,OFFSET .ws1        ; at label .ws1
  1079.     ret
  1080.  
  1081. .ws1:    mov    si,OFFSET line_buffer    ; SI -> string to be matched
  1082.     call    check_screen        ; see if it's there
  1083.     jnc    .ws4            ; if the string has appeared
  1084.  
  1085. .ws3:    mov    time_out,3        ; try again in 3 more ticks' time
  1086.  
  1087. .ws4:    ret
  1088. c_WaitScreen    endp
  1089.  
  1090. ;    WaitUntil <HH:MM> - wait until a given time of day
  1091.  
  1092. c_WaitUntil    proc    near
  1093.     cmp    pan_state,PS_RUNNING    ; cannot do this in background mode
  1094.     jae    .wu5
  1095.     call    decode_decimal        ; decode decimal tick count
  1096.     mov    hour,al            ; save hour (0-24)
  1097.     inc    si
  1098.     call    decode_decimal        ; decode decimal tick count
  1099.     mov    minute,al        ; save minute (0-60)
  1100.     mov    ax,18            ; check every second
  1101.     mov    bx,OFFSET .wu1        ; below
  1102.     ret
  1103.  
  1104. .wu1:    mov    ah,2Ch            ; DOS Get Time
  1105.     int    21h
  1106.     cmp    cx,until_time        ; has the due time come around?
  1107.     jne    .wu3            ; no, keep waiting
  1108.  
  1109. .wu2:    ret                ; yes, do next command
  1110.  
  1111. .wu3:    mov    ah,01h            ; check for keyboard input
  1112.     pushf
  1113.     call    [i_BIOS_kb]        ; int    16h
  1114.     jz    .wu4            ; if none
  1115.     xor    ax,ax            ; read that input
  1116.     pushf
  1117.     call    [i_BIOS_kb]        ; int    16h
  1118.     cmp    al,1Bh            ; Escape?
  1119.     jne    .wu4            ; ignore anything but
  1120.     cmp    pan_state,PS_RUNNING    ; running a program?
  1121.     je    .wu2            ; yes, skip to next command
  1122.     jmp    terminate        ; no, terminate the program
  1123.  
  1124. .wu4:    mov    time_out,18        ; wait another second
  1125.     ret
  1126.  
  1127. .wu5:    mov    si,OFFSET .wuA        ; "Command not valid during background operation"
  1128.     jmp    command_error
  1129.  
  1130. .wuA    db    'Command not valid during background operation',0
  1131. c_WaitUntil    endp
  1132.  
  1133. ;    Wipe - clear the screen
  1134.  
  1135. c_Wipe    proc    near
  1136.     mov    ah,0Fh            ; BIOS get video mode
  1137.     int    10h            ; returns AL = display mode
  1138.     mov    ah,00h            ; BIOS set video mode
  1139.     int    10h            ; which incidentally clears the screen
  1140.     xor    ax,ax            ; no continuation
  1141.     ret
  1142. c_Wipe    endp
  1143.  
  1144. ; Procedures for handling commands while command-processing is inhibited.
  1145.  
  1146. ;    Process any kind of IF command when processing suspended
  1147.  
  1148. n_If    proc    near
  1149.     inc    if_nest_level        ; one level of If/EndIf deeper
  1150.     ret
  1151. n_If    endp
  1152.  
  1153. ; Regular commands are no-ops
  1154.  
  1155. n_Nop    proc    near
  1156.     ret
  1157. n_Nop    endp
  1158.  
  1159.  
  1160. ;******************************************************************************
  1161. ;*                                                                            *
  1162. ;*                        Miscellaneous procedures                            *
  1163. ;*                                                                            *
  1164. ;******************************************************************************
  1165.  
  1166. ;    check_screen - checks if a given string appears at a given screen position
  1167. ;
  1168. ; Called with:
  1169. ;    SI -> string to be sought
  1170. ;    'screen_position' holding the row and column
  1171. ;
  1172. ; Returns:
  1173. ;    CF = 0 if string is found
  1174. ;     CF = 1 otherwise
  1175.  
  1176. check_screen    proc    near
  1177.     push    es
  1178.     mov    dx,screen_position    ; set PAN screen position
  1179.     call    set_video_address
  1180.  
  1181. .chs1:    cmp    BYTE PTR [si],0        ; check the next byte
  1182.     je    .chs3            ; if null we matched the whole string!
  1183.     mov    ax,es:[di]        ; AH = attribute, AL = character code
  1184.     cmp    [si],al            ; is character the one we want?
  1185.     jne    .chs2            ; no, so match fails...
  1186.     inc    di            ; yes, check next
  1187.     inc    di
  1188.     inc    si
  1189.     jmp    SHORT .chs1
  1190.  
  1191. .chs2:    stc                ; return CF set for failure
  1192.  
  1193. .chs3:    pop    es
  1194.     ret        ; returns CF = 0 if match else CF = 1
  1195. check_screen    endp
  1196.  
  1197. ;    command_error - spits out error information and quits.
  1198. ;
  1199. ; Called with:
  1200. ;    SI -> diagnostic (null terminated string)
  1201.  
  1202. command_error    proc    near    ; SI -> diagnostic message
  1203.     push    si            ; save diagnostic pointer
  1204.     cmp    pan_state,PS_LOADED    ; check the state
  1205.     jbe    .ce1            ; if PAN is in control
  1206.     cli                ; else turn off interrupts
  1207.  
  1208. ; Prepare screen for messages
  1209.  
  1210. .ce1:    mov    ah,0Fh            ; BIOS get video mode
  1211.     int    10h            ; returns AL = display mode
  1212.                     ; convert to a suitable mode
  1213.     xor    bx,bx
  1214.     mov    bl,al
  1215.     mov    al,[bomb_Mode+bx]    ; AL = safest text mode
  1216.     mov    ah,00h            ; BIOS set video mode
  1217.     int    10h            ; which incidentally clears the screen
  1218.  
  1219.     mov    si,OFFSET ferrmsg    ; "Fatal error in PAN Command:  "
  1220.     call    ttyz
  1221.     call    reconstruct_command    ; recreate text of command
  1222.     call    ttyz            ; and display it
  1223.     mov    si,OFFSET crlfz
  1224.     call    ttyz
  1225.     pop    si            ; display the specific diagnostic
  1226.     call    ttyz
  1227.     cmp    pan_state,PS_LOADED    ; check the state
  1228.     ja    .ce2            ; if PAN is not in control
  1229.     jmp    c_Quit            ; then get out quick
  1230.  
  1231. .ce2:    mov    si,OFFSET bomb_msg2    ; else wait for confirmation
  1232.     call    ttyz
  1233.     xor    ax,ax            ; wait for input
  1234.     pushf
  1235.     call    [i_BIOS_kb]        ; int    16h
  1236.     xor    ax,ax            ; do a warm boot
  1237.     mov    ds,ax
  1238.     mov    ax,1234h
  1239.     mov    ds:[472h],ax
  1240.     db    0EAh            ; JMP FFFF:0000
  1241.     dw    0000h, 0FFFFh
  1242.  
  1243. bomb_Mode    db    0,1,2,3,0,0,0,7,0,0, 0,11,12, 2, 2, 2, 2, 2, 7, 2
  1244. ;            0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19
  1245. bomb_msg2    db    CR,LF,'Press the [Space Bar] to reboot.',0
  1246. ferrmsg        db    'Fatal error in PAN command:',CR,LF,0
  1247. command_error    endp
  1248.  
  1249. ;    compare_strings
  1250. ;
  1251. ; Called with:
  1252. ;    SI and DI -> strings to be compared
  1253. ;    CX = length
  1254. ;
  1255. ; Returns:
  1256. ;    CX, SI and DI unchanged
  1257. ;    flags:  see CMPS instruction
  1258.     
  1259. compare_strings    proc    near
  1260.     push    si
  1261.     push    di
  1262.     push    cx
  1263.     repe    cmpsb
  1264.     pop    cx
  1265.     pop    di
  1266.     pop    si
  1267.     ret
  1268. compare_strings    endp
  1269.  
  1270. ;    Copy a null-terminated string.
  1271. ;
  1272. ; Called with:
  1273. ;    SI -> source string (null terminated)
  1274. ;    DI -> destination
  1275. ;
  1276. ; Returns:
  1277. ;    SI = garbage
  1278. ;    DI -> null at end of the copy
  1279.  
  1280. copyz    proc    near
  1281.  
  1282. .cz1:    lodsb                ; copy each byte including the null
  1283.     stosb
  1284.     test    al,al
  1285.     jnz    .cz1            ; continue until null
  1286.     dec    di            ; DI -> null at end of copied string
  1287.     ret
  1288. copyz    endp
  1289.  
  1290. ;    Copy a delimited terminated string to 'line_buffer'.
  1291. ;
  1292. ; Called with:
  1293. ;    SI -> "string"
  1294. ;
  1295. ; Returns:
  1296. ;    SI -> copy
  1297. ;    DI = garbage
  1298.  
  1299. copy_string    proc    near
  1300.     mov    di,OFFSET line_buffer    ; DI -> standard destination
  1301.     push    di
  1302.     lodsb                ; AL = delimiter
  1303.     mov    ah,al            ; keep in AH
  1304.  
  1305. .cs1:    lodsb                ; AL = next character
  1306.     test    al,al            ; allow missing closing delimiter
  1307.     jz    .cs3            ; if end of string
  1308.     cmp    al,ah            ; delimiter?
  1309.     jz    .cs3
  1310.     stosb
  1311.     jmp    SHORT .cs1
  1312.  
  1313. .cs3:    xor    ax,ax            ; store null terminator
  1314.     stosb
  1315.     pop    si            ; SI -> line_buffer
  1316.     ret        ; returns SI -> copied string
  1317. copy_string endp
  1318.  
  1319. ;    decode a decimal number
  1320. ;
  1321. ; Called with:
  1322. ;    SI -> numeric string
  1323. ;
  1324. ; Returns:
  1325. ;    SI -> first non-numeric character in string
  1326. ;    AX = decoded value
  1327.  
  1328. decode_decimal    proc    near
  1329.     xor    bx,bx            ; decode value into bx
  1330.     mov    cx,10            ; CL = 10, keep sign indication in CH
  1331.     cmp    BYTE PTR [si],'+'    ; initial + or - is allowed
  1332.     je    .dec0
  1333.     cmp    BYTE PTR [si],'-'
  1334.     jne    .dec1
  1335.     inc    ch
  1336.  
  1337. .dec0:    inc    si            ; push SI past sign
  1338.  
  1339. .dec1:    lodsb                ; AL = next character
  1340.     sub    al,'0'            ; check if it's a digit
  1341.     jl    .dec2
  1342.     cmp    al,9
  1343.     jg    .dec2
  1344.     xchg    ax,bx            ; AX = cumulative total
  1345.     mul    cl            ; multiply by ten
  1346.     add    bx,ax            ; and add in the new digit
  1347.     jmp    .dec1
  1348.  
  1349. .dec2:    mov    ax,bx            ; AX = decoded value for return
  1350.     test    ch,ch            ; + or -
  1351.     jz    .dec3            ; if +
  1352.     neg    ax            ; if - then negate it
  1353.  
  1354. .dec3:    dec    si            ; back up SI to first non-digit
  1355.     ret
  1356. decode_decimal    endp
  1357.  
  1358. ;    decode a hex number
  1359. ;
  1360. ; Called with:
  1361. ;    SI -> numeric string
  1362. ;
  1363. ; Returns:
  1364. ;    SI -> first non-numeric character in string
  1365. ;    AX = decoded value
  1366.  
  1367. decode_hex    proc    near
  1368.     xor    bx,bx            ; put decoded value in bx
  1369.  
  1370. .hex1:    lodsb                ; AL = next character
  1371.     cmp    al,'0'            ; check if it's a hexit
  1372.     jl    .hex2
  1373.     cmp    al,'9'
  1374.     jg    .hex2
  1375.     sub    al,'0'
  1376.  
  1377. .hex0:    mov    cl,4            ; multiply result so far by 16
  1378.     shl    bx,cl
  1379.     add    bx,ax            ; and add in the new hexit
  1380.     jmp    .hex1
  1381.  
  1382. .hex2:    call    isletter
  1383.     jc    .hex3            ; if not a letter
  1384.     cmp    al,'G'
  1385.     jae    .hex3
  1386.     sub    al,'A'-10
  1387.     jmp    .hex0
  1388.  
  1389. .hex3:    mov    ax,bx            ; set result in AX
  1390.     dec    si            ; point SI to terminator
  1391.     ret
  1392. decode_hex    endp
  1393.  
  1394. ;    delay - pause for a given count of clock ticks.
  1395. ;
  1396. ; Called with:
  1397. ;    CX = number of 18.2-to-a-second ticks
  1398.  
  1399. CLOCK    =    46Ch        ; low-memory timer word
  1400. delay    proc    near
  1401.     push    es            ; get ES = 0
  1402.     xor    ax,ax
  1403.     mov    es,ax
  1404.     mov    ax,es:[CLOCK]        ; AX = current clock value
  1405.  
  1406. .del1:    cmp    ax,es:[CLOCK]        ; count down changes in the clock
  1407.     je    .del1
  1408.     mov    ax,es:[CLOCK]
  1409.     loop    .del1
  1410.  
  1411.     pop    es
  1412.     ret
  1413. delay    endp
  1414.  
  1415. ;    display_string    - display a null-terminated string on the screen.
  1416. ;
  1417. ; Called with:
  1418. ;    DX = screen position
  1419. ;    BL = video attribute
  1420. ;    SI -> string
  1421.  
  1422. display_string    proc    near    ; DX = screen position, BL = video attribute
  1423.     push    es
  1424.     call    set_video_address    ; get ES:DI -> video buffer
  1425.     mov    ah,bl            ; AH = attribute
  1426.  
  1427. .ds1:    lodsb                ; AL = next character from string
  1428.     test    al,al            ; ends at a null
  1429.     jz    .ds2
  1430.     stosw                ; pop into video memory
  1431.     jmp    SHORT .ds1
  1432.  
  1433. .ds2:    pop    es
  1434.     ret
  1435. display_string    endp
  1436.  
  1437. ;    get_screen_position - decode a row-column spec.  Note that the row and
  1438. ;                  column numbers are counted from zero, and are
  1439. ;                  deliberately not checked for validity.
  1440. ; Called with:
  1441. ;     SI -> "<row> <column>"
  1442. ;
  1443. ; Stores the result in 'screen_position'.
  1444.  
  1445. get_screen_position    proc    near
  1446.     call    decode_decimal        ; decode row number
  1447.     mov    n_row,al
  1448.     call    skip_whitespace        ; skip separator
  1449.     call    decode_decimal        ; decode column number
  1450.     mov    n_col,al
  1451.     ret
  1452. get_screen_position    endp
  1453.  
  1454. ;    get_script - determines the script-file name from the command-line
  1455. ;             argument, loads and preprocesses the file.
  1456. ;
  1457. ; On return:
  1458. ;    AX = number of bytes read
  1459.  
  1460. get_script    proc    near
  1461.     mov    si,80h            ; SI -> command line
  1462.     xor    ax,ax            ; first character holds the lebgth
  1463.     lodsb
  1464.     mov    bx,ax            ; AX = BX = character count
  1465.     mov    [si+bx],ah        ; replace terminator with null
  1466.     call    skip_whitespace        ; skip any spaces
  1467.     mov    dx,OFFSET .gsB        ; "ERROR:  No script file specified"
  1468.     jz    .gs6            ; if no filename given
  1469.     mov    dx,si            ; DX -> filename
  1470.     xor    ax,ax
  1471.  
  1472. .gs1:    lodsb                ; see if name includes an extension
  1473.     cmp    al,'.'            ; that is a period
  1474.     jne    .gs2
  1475.     mov    ah,al            ; note period in AH
  1476.  
  1477. .gs2:    cmp    al,' '            ; take any control character as the end
  1478.     ja    .gs1            ; this is chancy but...
  1479.  
  1480.     cmp    ah,'.'
  1481.     je    .gs3            ; if an extension was given
  1482.     mov    di,si            ; else append the default
  1483.     dec    di
  1484.     mov    si,OFFSET pan_extension
  1485.     mov    cx,5            ; which is 5 characters long with null
  1486.     rep    movsb
  1487.  
  1488. .gs3:    mov    ax,3D00h        ; open the command file
  1489.     int    21h
  1490.     mov    dx,OFFSET .gsC
  1491.     jc    .gs7            ; if open returned an error
  1492.     mov    file_handle,ax        ; else save the handle
  1493.     call    load_script        ; load the script from the file
  1494.     jc    .gs7            ; if there was something wrong with it
  1495.     mov    ah,3Eh            ; DOS close file
  1496.     mov    bx,file_handle
  1497.     int    21h
  1498.     cmp    if_nest_level,0
  1499.     jnz    .gs4            ; if Ifs and EndIfs don't match
  1500.     mov    ax,di            ; return size of script
  1501.     sub    ax,OFFSET script_buffer
  1502.     clc
  1503.     ret
  1504.  
  1505. .gs4:    mov    dx,OFFSET .gsD        ; complain
  1506.  
  1507. .gs6:    stc
  1508.  
  1509. .gs7:    ret
  1510.  
  1511. .gsB    db    'PAN Error:  No script file specified$'
  1512. .gsC    db    'PAN Error:  Cannot find script file$'
  1513. .gsD    db    "PAN Error:  Unbalanced Ifs and EndIfs$"
  1514. get_script    endp
  1515.  
  1516. ;    is_digit - checks if character is an ASCII-coded digit
  1517. ;
  1518. ; Called with:
  1519. ;    AL = character
  1520. ;
  1521. ; Returns:
  1522. ;    CF = 0 if character is a digit ('0' - '9')
  1523. ;    CF = 1 otherwise
  1524.  
  1525. is_digit    proc    near
  1526.     cmp    al,'0'            ; is it a numeric ASCII code?
  1527.     jb    .id1
  1528.     cmp    al,'9'
  1529.     ja    .id1
  1530.     clc
  1531.     ret
  1532.  
  1533. .id1:    stc
  1534.     ret
  1535. is_digit    endp
  1536.  
  1537. ;    isletter - check and fold a letter
  1538. ;
  1539. ; Called with:
  1540. ;    al = ASCII code
  1541. ;
  1542. ; Returns:
  1543. ;    CF = 0 if AL contains a letter
  1544. ;            1 otherwise
  1545. ;    AL = ASCII code, folded to uppercase if letter
  1546.  
  1547. isletter    proc    near
  1548.     cmp    al,'A'
  1549.     jb    .let1
  1550.     cmp    al,'Z'
  1551.     jbe    .let2
  1552.     cmp    al,'a'
  1553.     jb    .let1
  1554.     cmp    al,'z'
  1555.     ja    .let1
  1556.  
  1557. .let2:    and    al,0DFh            ; fold
  1558.     ret
  1559.  
  1560. .let1:    stc
  1561.     ret
  1562. isletter    endp
  1563.  
  1564. ;     loader - attempt to load a target program given a filename.
  1565. ;
  1566. ; Called with:
  1567. ;    SI -> program filename
  1568.  
  1569. loader    proc    near
  1570.     call    normalize        ; copy filename and arguments
  1571.     mov    ax,4B01h        ; DOS Load Program and Return function
  1572.     mov    bx,OFFSET parameter_block    ; BX -> parameter block
  1573.     mov    dx,si            ; DX -> filename
  1574.     int    21h            ; returns in child context
  1575.     jc    .load1            ;     unless load attempt failed
  1576.     mov    child_size,bx        ; save size of program
  1577.     mov    ah,51h            ; DOS get PSP address
  1578.     int    21h            ; returns BX = segment of PSP
  1579.     mov    child_psp,bx        ; save that
  1580.     mov    al,50h            ; DOS set PSP address
  1581.     mov    bx,cs            ; set process back to us
  1582.     int    21h
  1583.     mov    pan_state,PS_LOADED    ; set state to PS_LOADED
  1584.     clc
  1585.  
  1586. .load1:    ret
  1587. loader    endp
  1588.  
  1589. ;    load_script - loads the script from a given opened file.
  1590. ;
  1591. ; Called with:
  1592. ;     'file_handle' containing the handle of the file.
  1593. ;
  1594. ; Returns:
  1595. ;    CF = 0 if script was loaded succesfully
  1596. ;    CF = 1 if an error occurred
  1597.  
  1598. load_script    proc    near
  1599.     mov    di,OFFSET script_buffer
  1600.  
  1601. .ls1:    mov    bx,file_handle
  1602.     call    read_line        ; read one line = one command
  1603.     jc    .ls3            ; on EOF
  1604.     call    skip_whitespace        ; skip any initial blanks
  1605.     test    al,al            ; blank line?
  1606.     jz    .ls1            ; yes, ignore it
  1607.     cmp    al,'*'            ; comment line?
  1608.     je    .ls1            ; yes, ignore it
  1609.     mov    bx,OFFSET command_keys    ; identify the command
  1610.     call    match_key        ; returns AL = command index if valid
  1611.     jnz    .ls4            ; if it's invalid
  1612.     push    di            ; save pointer to start of command
  1613.     inc    di            ; reserve a byte for command length
  1614.     stosb                ; store command index
  1615.     call    skip_whitespace        ; skip any blanks after command
  1616.  
  1617. .ls2:    lodsb                ; copy the rest of the line
  1618.     stosb
  1619.     test    al,al            ; including the null terminator
  1620.     jnz    .ls2
  1621.     pop    bx            ; BX -> start of command
  1622.     mov    ax,di            ; AX -> end of command
  1623.     sub    ax,bx            ; AX = length of command
  1624.     mov    [bx],al            ; store that
  1625.  
  1626. ; Do If/EndIf checking
  1627.  
  1628.     xor    ax,ax
  1629.     inc    bx
  1630.     mov    al,[bx]            ; AX = command index
  1631.     push    ax
  1632.     mul    command_entry_size
  1633.     mov    bx,ax            ; BX = offset of command table entry
  1634.     xor    ax,ax            ; get AX = the command type
  1635.     mov    al,BYTE PTR [command_table+PC_TYPE+bx]
  1636.     mov    bx,ax
  1637.     pop    ax            ; call preprocessor with AX = index
  1638.     call    [preprocessing_table+bx]
  1639.     jnc    .ls1            ; if no error
  1640.     ret                ; else return with CF set
  1641.  
  1642. .ls3:    xor    ax,ax            ; zero-length command at end of script
  1643.     stosw
  1644.     ret
  1645.  
  1646. .ls4:    call    ttyz            ; display the offending line
  1647.     mov    dx,OFFSET .lsA        ; DX -> "Invalid command"
  1648.     stc
  1649.     ret
  1650.  
  1651. .lsA    db    CR,LF,'PAN Error:  Invalid command.$'
  1652. load_script    endp
  1653.  
  1654. ; Procedures for preprocessing commands:
  1655.  
  1656. pp_regular    proc    near    ; for regular commands there is nothing to do
  1657.     cmp    al,LABEL_INDEX        ; unless this was a label command
  1658.     jne    .ppr1
  1659.     mov    al,3            ; store length for a SetIf
  1660.     stosb
  1661.     mov    al,SETIF_INDEX        ; insert a SetIf
  1662.     stosb
  1663.     mov    al,if_nest_level
  1664.     stosb
  1665.  
  1666. .ppr1:    clc
  1667.     ret
  1668. pp_regular    endp
  1669.  
  1670. pp_If    proc    near        ; for Ifs increment the nest level
  1671.     inc    if_nest_level
  1672.     clc
  1673.     ret
  1674. pp_If    endp
  1675.  
  1676. pp_Else    proc    near        ; for Else ensure it's in an If block
  1677.     cmp    if_nest_level,0
  1678.     jnz    .ppe1
  1679.     mov    dx,OFFSET .ppeA        ; complain about misplaced Else
  1680.     stc
  1681.  
  1682. .ppe1:    ret
  1683.  
  1684. .ppeA    db    "PAN Error:  'Else' command not in If/EndIf clause$"
  1685. pp_Else    endp
  1686.  
  1687. pp_EndIf    proc    near    ; For EndIf decrement the nest level
  1688.     cmp    if_nest_level,0
  1689.     jnz    .ppf1
  1690.     mov    dx,OFFSET .ppfA        ; complain about dangling EndIf
  1691.     stc
  1692.     ret
  1693.  
  1694. .ppf1:    dec    if_nest_level
  1695.     clc
  1696.     ret
  1697.  
  1698. .ppfA    db    "Error:  EndIf found with no matching If$"
  1699. pp_EndIf    endp
  1700.  
  1701. ;     match_key - match a string to a set of keys.  The comparison is for
  1702. ;            letters only and is case insensitive.
  1703. ;
  1704. ; Called with:
  1705. ;     BX -> list of keys
  1706. ;    SI -> string to be matched
  1707. ;
  1708. ; Returns:
  1709. ;    If match made:  ZR = 1 and AX = index of the key
  1710. ;    Else:  ZR = 0
  1711.  
  1712. match_key    proc    near
  1713.     push    di
  1714.     call    skip_whitespace        ; skip any leading blanks
  1715.     mov    di,si            ; SI, DI -> first non-white char
  1716.     xor    cx,cx            ; count keys in CX
  1717.  
  1718. .mat1:    mov    si,di            ; SI -> target of match
  1719.  
  1720. .mat2:    mov    ah,[bx]            ; AH = character to compare against
  1721.     inc    bx            ; bump the pointer
  1722.     test    ah,ah            ; check for end of key
  1723.     jz    .mat4            ; we got a match
  1724.     lodsb                ; AL = next character of string
  1725.     cmp    al,' '            ; match up to blank or control char
  1726.     jbe    .mat3
  1727.     cmp    al,ah            ; do the real comparison
  1728.     je    .mat2            ; if they match then keep trying
  1729.     xor    al,20h            ; else switch case of string char
  1730.     cmp    al,ah            ; and compare that way
  1731.     je    .mat2
  1732.  
  1733. .mat3:    cmp    BYTE PTR [bx],0        ; push BX to end of current key
  1734.     pushf
  1735.     inc    bx
  1736.     popf
  1737.     jnz    .mat3
  1738.     inc    cx            ; increment key counter
  1739.     cmp    BYTE PTR [bx],0        ; have we tried all keys?
  1740.     jnz    .mat1            ; no, try next
  1741.  
  1742. .mat35:    mov    si,di            ; no match, return SI as it was
  1743.     inc    cx            ; just to ensure that ZR = 0
  1744.     pop    di
  1745.     ret        ; no match:  return ZR = 0, SI as on entry
  1746.  
  1747. .mat4:    lodsb                ; AL = next character of string
  1748.     cmp    al,' '            ; it should be blank or control char
  1749.     ja    .mat35
  1750.     dec    si
  1751.     xor    ax,ax            ; set ZR
  1752.     mov    ax,cx            ; AX = key number
  1753.     pop    di
  1754.     ret        ; match: return ZR = 1, AX = key number
  1755. match_key    endp        ;        and SI -> character past key
  1756.  
  1757.  
  1758. ;     normalize - normalize translates a string containing control characters
  1759. ;            in the form '^X' while copying it to line_buffer.
  1760. ;
  1761. ; Called with:
  1762. ;    SI -> delimited string
  1763. ;
  1764. ; Returns:
  1765. ;    SI -> normalized string in 'line_buffer'
  1766. ;    DI -> end of normalized string
  1767. ;    CX = length
  1768.  
  1769. normalize    proc    near
  1770.     mov    di,OFFSET line_buffer
  1771.     push    di
  1772.     xor    ax,ax
  1773.     lodsb                ; AL = delimiter
  1774.     or    ah,al            ; keep in AH
  1775.     jz    .nor3            ; if no argument
  1776.  
  1777. .nor1:    lodsb                ; AL = next character
  1778.     test    al,al
  1779.     jz    .nor3            ; if end of input
  1780.     cmp    al,ah            ; end of delimited string?
  1781.     je    .nor3
  1782.     cmp    al,' '
  1783.     jb    .nor1            ; ignore "real" control characters
  1784.     cmp    al,'^'
  1785.     jne    .nor2
  1786.     lodsb
  1787.     cmp    al,'^'            ; ^^ means ^
  1788.     je    .nor2
  1789.     and    al,1Fh            ; make a control
  1790.  
  1791. .nor2:    stosb                ; and store into string
  1792.     jmp    SHORT .nor1
  1793.  
  1794. .nor3:    xor    ax,ax            ; store null terminator
  1795.     stosb
  1796.     pop    si            ; SI -> line_buffer
  1797.     mov    cx,di            ; calculate new length
  1798.     sub    cx,si
  1799.     ret        ; returns SI -> normalized string, CX = length
  1800.             ;    DI -> end of normalized string
  1801. normalize endp
  1802.  
  1803. ;     read_line - read one line from a file into line_buffer.
  1804. ;
  1805. ; Called with:
  1806. ;    BX = file handle
  1807. ;
  1808. ; Returns:
  1809. ;    If data read then:  CF = 0, SI -> line, CX = length
  1810. ;    Else CF = 1 (implies end-of-file)
  1811.  
  1812. read_line    proc    near
  1813.     mov    si,OFFSET line_buffer    ; SI -> line_buffer
  1814.     mov    cx,1            ; read one byte at a time
  1815.  
  1816. .re1:    mov    ah,3Fh            ; DOS read function
  1817.     mov    dx,si            ; DS:DX -> buffer
  1818.     int    21H
  1819.     jc    .re5            ; if read error
  1820.     test    ax,ax
  1821.     jz    .re4            ; if EOF
  1822.     mov    al,[si]            ; AL = byte just read
  1823.     cmp    al,' '            ; control character?
  1824.     jb    .re2            ; if so
  1825.     inc    si            ; else bump buffer pointer
  1826.     cmp    si,OFFSET line_buffer+127; and check for overflow
  1827.     jb    .re1            ; handle over-long lines ungracefully!
  1828.  
  1829. .re3:    xor    ax,ax            ; null terminate the line
  1830.     mov    [si],al
  1831.     mov    cx,si            ; calculate its length
  1832.     mov    si,OFFSET line_buffer    ; SI -> line_buffer
  1833.     sub    cx,si            ; CX = line length
  1834.     clc
  1835.     ret        ; return with CF zero and SI -> input, CX = length
  1836.  
  1837. .re2:    cmp    al,CR            ; check for CR
  1838.     jne    .re1            ; and discard other control characters
  1839.     jmp    SHORT .re3        ; end the line on CR
  1840.  
  1841. .re4:    cmp    si,OFFSET line_buffer    ; accept a last line with no CR
  1842.     jne    .re3
  1843.  
  1844. .re5:    stc
  1845.     ret        ; EOF or read error, return with CF set
  1846. read_line    endp
  1847.  
  1848. ;    reconstruct_command - reconstruct the text form of the current
  1849. ;                command.
  1850. ;
  1851. ; Returns:
  1852. ;    SI -> command key
  1853.  
  1854. reconstruct_command    proc    near
  1855.     mov    di,OFFSET line_buffer    ; reconstruction done here
  1856.     push    di            ; save a copy for later
  1857.     mov    si,current_command    ; SI -> internal form of command
  1858.     xor    ax,ax            ; get AX = command index
  1859.     lodsb
  1860.     mul    command_entry_size    ; calculate AX = offset of entry
  1861.     push    si
  1862.     mov    si,ax
  1863.     mov    si,WORD PTR [command_table+PC_KEY+si]
  1864.     call    copyz            ; copy null-terminated string
  1865.     mov    al,' '            ; put in a blank
  1866.     stosb
  1867.     pop    si
  1868.     call    copyz            ; and copy the arguments
  1869.     pop    si            ; return SI -> reconstructed text
  1870.     ret
  1871. reconstruct_command    endp
  1872.  
  1873. ;    resolve jumps - replace labels in Jump commands with offsets.
  1874.  
  1875. resolve_jumps    proc    near
  1876.  
  1877. .rj1:    mov    si,command_ptr        ; SI -> next command
  1878.     xor    ax,ax
  1879.     lodsb                ; AX = command length
  1880.     test    ax,ax
  1881.     jz    .rj4            ; at end of script
  1882.     add    command_ptr,ax        ; update the command pointer
  1883.     lodsb                ; AX = command index
  1884.     cmp    al,JUMP_INDEX        ; is it a jump?
  1885.     jne    .rj1
  1886.  
  1887.     mov    di,si            ; DI -> target label
  1888.     mov    si,OFFSET script_buffer    ; scan through script for label
  1889.     xor    cx,cx
  1890.  
  1891. .rj2:    add    si,cx            ; SI -> next command
  1892.     xor    ax,ax
  1893.     lodsb                ; AX = length of current command
  1894.     test    ax,ax
  1895.     jz    .rj3            ; at end of script
  1896.     sub    ax,2
  1897.     mov    cx,ax            ; CX = length - 2
  1898.     lodsb                ; AX = command index
  1899.     cmp    al,LABEL_INDEX        ; is it a label?
  1900.     jne    .rj2
  1901.     call    compare_strings
  1902.     jne    .rj2
  1903.     add    si,cx            ; SI -> next command
  1904.     mov    [di],si            ; overwrite label in jump
  1905.     jmp    SHORT .rj1
  1906.  
  1907. .rj3:    mov    si,di            ; SI -> label
  1908.     call    ttyz            ; display the offending line
  1909.     mov    dx,OFFSET .rjA        ; DX -> "ERROR:  Label not found."
  1910.     stc
  1911.  
  1912. .rj4:    ret
  1913.  
  1914. .rjA    db    CR,LF,'PAN Error:  Label not found.$'
  1915. resolve_jumps    endp
  1916.  
  1917. ;    restore_vector - restores a value into an interrupt vector
  1918. ;
  1919. ; On entry:
  1920. ;    AL = vector number
  1921. ;    DS:BX = address at old vector is stored
  1922. ;
  1923. ; Destroys AX.
  1924.  
  1925. restore_vector    proc    near
  1926.     push    si
  1927.     push    es
  1928.     xor    ah,ah            ; calculate offset of vector
  1929.     shl    ax,1            ;     = number * 4
  1930.     shl    ax,1
  1931.     mov    si,ax            ; SI = offset of vector
  1932.     xor    ax,ax
  1933.     mov    es,ax            ; ES:SI -> vector
  1934.     pushf
  1935.     cli                ; interrupts off during switch
  1936.     mov    ax,[bx]            ; move in the saved value
  1937.     mov    es:[si],ax
  1938.     mov    ax,[bx+2]
  1939.     mov    es:[si+2],ax
  1940.     popf
  1941.     pop    es
  1942.     pop    si
  1943.     ret
  1944. restore_vector    endp
  1945.  
  1946. ;    run_it - transfer control to child program.
  1947.  
  1948. run_it    proc    near
  1949.     mov    ax,5000h        ; DOS set PSP address
  1950.     mov    bx,child_psp        ; BX = PSP of loaded program
  1951.     int    21h
  1952.  
  1953.     cli
  1954.     mov    pan_sp,sp        ; save own SP
  1955.     mov    ss,child_ss        ; set child's stack
  1956.     mov    sp,child_sp
  1957.     sti
  1958.  
  1959.     pop    ax            ; dump original drive valid flag
  1960.     mov    ax,cx            ; set real drive valid flag
  1961.     push    child_cs        ; set stack to "return" to child
  1962.     push    child_ip
  1963.  
  1964.     mov    es,child_psp
  1965.     mov    ds,child_psp        ; DS = ES = child PSP
  1966.  
  1967.     mov    WORD PTR es:[000AH],OFFSET child_return
  1968.  
  1969.     xor    bx,bx
  1970.     xor    dx,dx
  1971.     xor    bp,bp
  1972.     xor    si,si
  1973.     xor    di,di
  1974.     mov    cs:in_pan_flag,bl    ; clear in-Pan flag
  1975.     retf        ; Note that we are in a NEAR procedure
  1976.  
  1977. child_return:        ; returns in PAN context except DS = ???
  1978.             ; SP restored from Load operation not from Go
  1979.     mov    ax,cs            ; make sure DS and ES are set
  1980.     mov    ds,ax
  1981.     mov    es,ax
  1982.     mov    sp,pan_sp        ; restore SP saved just above
  1983.     call    unset_traps        ; should this be here ??? ***
  1984.     mov    al,pan_state        ; check state while resetting it
  1985.     cmp    al,PS_OBIT        ; waiting for this?
  1986.     je    .cr1            ; yes, continue
  1987.     mov    pan_state,PS_QUIT    ; set state so death can occur and
  1988.     jmp    c_Quit            ;    quit if chump did not wait for die
  1989.  
  1990. .cr1:    mov    pan_state,PS_INITIAL    ; revert to initial state
  1991.     xor    ax,ax
  1992.     ret
  1993. run_it    endp
  1994.  
  1995. ;    set_traps - capture the timer and BIOS-keyboard-function interrupts.
  1996.  
  1997. set_traps    proc    near
  1998.     mov    dx,OFFSET timer_intercept    ; replace timer interrupt
  1999.     mov    al,8h
  2000.     mov    bx,OFFSET i_timer
  2001.     call    set_vector
  2002.  
  2003.     mov    dx,OFFSET BIOS_kb_intercept    ; replace BIOS-kb interrupt
  2004.     mov    al,16h
  2005.     mov    bx,OFFSET i_BIOS_kb
  2006.     call    set_vector
  2007.     ret
  2008. set_traps    endp
  2009.  
  2010. ;    set_vector - copies the contents of an interrupt vector then stores
  2011. ;             a new value in the vector.
  2012. ;
  2013. ; On entry:
  2014. ;    AL = vector number
  2015. ;    DS:DX = new address for interrupt vector
  2016. ;    DS:BX = address at which to store old vector
  2017. ;
  2018. ; Destroys AX and BX.
  2019.  
  2020. set_vector    proc    near
  2021.     push    si
  2022.     push    es
  2023.     xor    ah,ah            ; calculate offset of vector
  2024.     shl    ax,1            ;     = number * 4
  2025.     shl    ax,1
  2026.     mov    si,ax            ; SI = offset of vector
  2027.     xor    ax,ax
  2028.     mov    es,ax            ; ES:SI -> vector
  2029.     pushf
  2030.     cli                ; interrupts off during switch
  2031.     mov    ax,es:[si]        ; move out the old
  2032.     mov    [bx],ax
  2033.     mov    ax,es:[si+2]
  2034.     mov    [bx+2],ax
  2035.     mov    es:[si],dx        ; move in the new
  2036.     mov    ax,ds
  2037.     mov    es:[si+2],ax
  2038.     popf
  2039.     pop    es
  2040.     pop    si
  2041.     ret
  2042. set_vector    endp
  2043.  
  2044. ;    set_video_address - set the video address corresponding to a given
  2045. ;                row and column.
  2046. ;
  2047. ; Called with:
  2048. ;    DX = screen position (DH = row, DL = column)
  2049. ;
  2050. ; Returns:
  2051. ;    ES:DI -> corresponding word in video buffer memory
  2052.  
  2053. set_video_address    proc    near    ; DX = screen position
  2054.     mov    ax,video_segment
  2055.     mov    es,ax
  2056.     xor    di,di            ; ES:DI -> start of video buffer
  2057.     mov    al,dh            ; DH = row number
  2058.     mul    screen_columns
  2059.     xor    dh,dh
  2060.     add    ax,dx
  2061.     add    di,ax
  2062.     add    di,ax
  2063.     ret        ; returns ES:DI -> word in video buffer
  2064. set_video_address    endp
  2065.  
  2066. ;    skip_whitespace - skip blanks and tabs in a string.
  2067. ;
  2068. ; Called with:
  2069. ;    SI -> string
  2070. ;
  2071. ; Returns:
  2072. ;    SI -> first character that is neither a blank nor a tab
  2073. ;    AL = that character
  2074.  
  2075. skip_whitespace    proc    near
  2076.  
  2077. .sw1:    lodsb
  2078.     cmp    al,' '
  2079.     je    .sw1
  2080.     cmp    al,09h            ; check for TAB
  2081.     je    .sw1
  2082.     dec    si
  2083.     test    al,al
  2084.     ret        ; returns SI -> first non-white char, AL = said char
  2085. skip_whitespace    endp    ;    and ZR = 1 if character is a null
  2086.  
  2087. ;    stuff_keys - stuff keycodes into the BIOS keyboard buffer.
  2088.  
  2089. stuff_keys    proc    near
  2090.     pushf                ; save interrupt flag
  2091.     push    es
  2092.     mov    es,kbb_segment        ; ES = keyboard-buffer segment
  2093.     cli                ; no interrupts while poking key buffer
  2094.  
  2095. .sk0:    mov    bx,es:[KBB_TAIL]    ; get tail
  2096.     mov    di,bx            ; and copy
  2097.     inc    bx            ; bump tail pointer
  2098.     inc    bx
  2099.     cmp    bx,es:[KBB_END]
  2100.     jne    .sk1
  2101.     mov    bx,es:[KBB_START]    ; if wrapped around
  2102.  
  2103. .sk1:    cmp    bx,es:[KBB_HEAD]    ; any room in buffer
  2104.     mov    ax,1            ; for timeout
  2105.     je    .sk3            ; if not...
  2106.  
  2107.     pop    es
  2108.     mov    si,kiq_first        ; SI -> string of key codes
  2109.     call    translate        ; translate next character
  2110.     mov    kiq_first,si        ; update pointer
  2111.     push    es
  2112.     jc    .sk4            ; if at end of string
  2113.     mov    es,kbb_segment        ; ES = keyboard-buffer segment
  2114.     stosw                ; store scan code and ASCII to KBB
  2115.     mov    es:[KBB_TAIL],bx    ; update tail
  2116.  
  2117.     mov    ax,type_rate        ; AX = inter-key delay (in ticks)
  2118.     test    ax,ax
  2119.     jz    .sk0            ; if zero just continue
  2120.  
  2121. .sk3:    mov    time_out,ax        ; set new timeout
  2122.  
  2123. .sk4:    pop    es
  2124.     popf
  2125.     ret
  2126. stuff_keys    endp
  2127.  
  2128. ;    terminate - terminate the current program.
  2129.  
  2130. terminate    proc    near
  2131.     mov    ax,4C00h        ; DOS terminate a program
  2132.     int    21h
  2133. terminate    endp
  2134.  
  2135. ;    translate - translates a character in keyboard format
  2136. ;
  2137. ; Called with:
  2138. ;    SI -> string of encoded key symbols
  2139. ;
  2140. ; Returns:
  2141. ;    CF = 0 if character available, and
  2142. ;        AX = key code suitable for insertion into BIOS keyboard buffer
  2143. ;        DL = shift status for character
  2144. ;    CF = 1 if end-of-string
  2145.  
  2146. translate    proc    near    ; SI -> key spec
  2147.     push    bx            ; save all registers but those
  2148.     push    cx            ;    used to return stuff
  2149.     push    di
  2150.  
  2151. .tra1:    xor    dx,dx            ; prepare DL to hold shift information
  2152.  
  2153. ; We start by checking for a caret which is usually a Ctrl-shift indicator
  2154.  
  2155. .tra2:    cmp    [si],BYTE PTR '^'    ; Ctrl-shifted character?
  2156.     jne    .tra3
  2157.     inc    si
  2158.     cmp    [si],BYTE PTR '^'    ; doubled?
  2159.     je    .tra8            ; send character
  2160.     or    dl,04h            ; set "Ctrl key is down" bit in status
  2161.  
  2162. .tra3:    cmp    [si],BYTE PTR '['    ; special-key delimiter?
  2163.     jne    .tra8
  2164.     inc    si            ; push pointer past \
  2165.     cmp    [si],BYTE PTR '['    ; doubled?
  2166.     je    .tra8            ; '[[' means '['
  2167.     mov    bx,si            ; save pointer to '['
  2168.  
  2169. .tra4:    lodsb                ; search for closing ']'
  2170.     test    al,al            ; or end of string
  2171.     jz    .tra6            ; if no closing ']'
  2172.     cmp    al,']'
  2173.     jne    .tra4
  2174.     dec    si
  2175.     mov    BYTE PTR [si],0        ; replace the ']' with a null
  2176.     mov    si,bx            ; SI -> keyname
  2177.     mov    bx,OFFSET shiftname_list; check the list of shift-key names
  2178.     call    match_key        ; look it up
  2179.     jne    .tra5            ; if no match
  2180.     inc    si            ; push SI past the null
  2181.     mov    bx,ax
  2182.     or    dl,[shiftbits+bx]    ; or bit for shift key into DL
  2183.     jmp    SHORT .tra2
  2184.  
  2185. .tra5:    mov    bx,OFFSET keyname_list    ; try other named keys
  2186.     call    match_key        ; look it up
  2187.     jne    .tra6            ; if no match
  2188.     inc    si            ; push SI past the null
  2189.     mov    bx,ax
  2190.     mov    ah,[key_scans+bx]    ; AH = scan code
  2191.     xor    al,al            ; AL = zero
  2192.     jmp    SHORT .tra9
  2193.  
  2194. .tra6:    mov    al,[si]            ; AL = character following '['
  2195.     call    is_digit        ; only valid thing now is a decimal
  2196.     jc    .tra8            ;   code of exactly three digits
  2197.     xor    ax,ax
  2198.     call    decode_decimal        ; decode the code
  2199.     cmp    BYTE PTR [si],']'
  2200.     stc
  2201.     jne    .tra12
  2202.     inc    si            ; push SI past ']'
  2203.     test    al,al
  2204.     jz    .tra1            ; zero is invalid
  2205.     test    ah,ah
  2206.     jz    .tra11            ; accept only codes between 1 and 127
  2207.  
  2208. .tra7:    jmp    .tra1            ; need a long jump here
  2209.  
  2210. .tra8:    xor    ax,ax            ; load and return literal ASCII
  2211.     lodsb
  2212.     test    al,al            ; test for end of string
  2213.     stc                ; at end we return with CF set
  2214.     jz    .tra12
  2215.     js    .tra11            ; if extended ASCII (no scan code)
  2216.     mov    bx,ax
  2217.     mov    ah,[scan+bx]        ; AH = scan code
  2218.     xor    bx,bx            ; check if we need to add a Shift
  2219.     mov    bl,ah
  2220.     add    bx,bx
  2221.     add    bx,OFFSET No_shift
  2222.     cmp    [bx],al
  2223.     je    .tra9            ; if char matches without a Shift
  2224.     or    dl,02h            ; assume a Left Shift
  2225.  
  2226. ; Convert ASCII and scan codes according to shifts
  2227.  
  2228. .tra9:    test    dl,08h            ; Alt takes precedence
  2229.     mov    bx,OFFSET Alt_shift
  2230.     jnz    .tra10
  2231.     test    dl,04h            ; Ctrl is next
  2232.     mov    bx,OFFSET Ctrl_shift
  2233.     jnz    .tra10
  2234.     test    dl,03h            ; Shift is lowest
  2235.     mov    bx,OFFSET Shift_shift
  2236.     jnz    .tra10
  2237.     mov    bx,OFFSET No_shift
  2238.  
  2239. .tra10:    xchg    al,ah            ; get scan code in AL
  2240.     xor    ah,ah
  2241.     add    ax,ax            ; convert to word index
  2242.     add    bx,ax            ; BX -> entry in shift table
  2243.     mov    ax,[bx]            ; load revised codes
  2244.     test    ax,ax            ; zero entry means key combination
  2245.     jz    .tra7            ;    generates nothing
  2246.  
  2247. .tra11:    clc                ; return character and CF = 0
  2248.  
  2249. .tra12:    pop    di
  2250.     pop    cx
  2251.     pop    bx
  2252.     ret
  2253. translate    endp
  2254.  
  2255. ;    ttyz - display a null-terminated string at the cursor using the BIOS.
  2256. ;
  2257. ; Called with:
  2258. ;    SI -> string
  2259.  
  2260. ttyz    proc    near
  2261.     xor    bx,bx            ; assume page 0
  2262.  
  2263. .tz1:    lodsb                ; do it one character at a time
  2264.     test    al,al
  2265.     jz    .tz2
  2266.     mov    ah,0Eh            ; using the BIOS
  2267.     int    10h
  2268.     jmp    SHORT .tz1
  2269.  
  2270. .tz2:    ret
  2271. ttyz    endp
  2272.  
  2273. ;    unset_traps - remove traps set by set_traps.
  2274.  
  2275. unset_traps    proc    near
  2276.     mov    ax,x_timer_offset    ; were traps set?
  2277.     or    ax,x_timer_segment
  2278.     jz    .uns1            ; skip if not
  2279.     mov    al,8h            ; remove timer intercept
  2280.     mov    bx,OFFSET i_timer
  2281.     call    restore_vector
  2282.     mov    al,16h            ; remove BIOS-keyboard intercept
  2283.     mov    bx,OFFSET i_BIOS_kb
  2284.     call    restore_vector
  2285.  
  2286. .uns1:    ret
  2287. unset_traps    endp
  2288.  
  2289. ; Interrupt stack
  2290.  
  2291.     dw    80h DUP (0)
  2292. interrupt_stack    LABEL    WORD        ; stack used within interrupts
  2293.  
  2294. script_buffer    db    0        ; script loaded starting here
  2295. code    ends
  2296.     end    start
  2297.